package view_server

import (
	"bigdevops/src/cache"
	"bigdevops/src/common"
	"bigdevops/src/config"
	"bigdevops/src/models"
	"bytes"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"go.uber.org/zap"
	"io"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
	yamlutil "k8s.io/apimachinery/pkg/util/yaml"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/restmapper"
	"strconv"
	"strings"
)

func createK8sYamlTask(c *gin.Context) {

	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)
	var reqObj models.K8sYamlTask
	err := c.ShouldBindJSON(&reqObj)
	if err != nil {
		sc.Logger.Error("解析新增k8s-yaml任务请求失败", zap.Any("k8s配置", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	// 在这里校验字段，是否必填，范围是否正确
	err = validate.Struct(reqObj)
	if err != nil {

		// 这里为什么要判断错误是否是 ValidationErrors
		if errors, ok := err.(validator.ValidationErrors); ok {
			common.ReqBadFailWithWithDetailed(

				gin.H{
					"翻译前": err.Error(),
					"翻译后": errors.Translate(trans),
				},
				"请求出错",
				c,
			)
			return
		}
		common.ReqBadFailWithMessage(err.Error(), c)
		return

	}

	userName := c.MustGet(common.GIN_CTX_JWT_USER_NAME).(string)
	dbUser, err := models.GetUserByUserName(userName)
	if err != nil {
		sc.Logger.Error("通过token解析到的userName去数据库中找User失败",
			zap.Error(err),
		)
		common.ReqBadFailWithMessage(fmt.Sprintf("通过token解析到的userName去数据库中找User失败:%v", err.Error()), c)
		return
	}

	reqObj.Status = common.K8S_YAMLTASK_STATUS_PENDING

	reqObj.UserID = dbUser.ID

	// 下面级联创建
	err = reqObj.CreateOne()
	if err != nil {
		sc.Logger.Error("新增k8s-yaml任务数据库失败", zap.Any("k8s-yaml任务", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	common.OkWithMessage("创建成功", c)

}

func updateK8sYamlTask(c *gin.Context) {

	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)
	var reqObj models.K8sYamlTask
	err := c.ShouldBindJSON(&reqObj)
	if err != nil {
		sc.Logger.Error("解析新增k8s-yaml任务请求失败", zap.Any("k8s配置", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	// 在这里校验字段，是否必填，范围是否正确
	err = validate.Struct(reqObj)
	if err != nil {

		// 这里为什么要判断错误是否是 ValidationErrors
		if errors, ok := err.(validator.ValidationErrors); ok {
			common.ReqBadFailWithWithDetailed(

				gin.H{
					"翻译前": err.Error(),
					"翻译后": errors.Translate(trans),
				},
				"请求出错",
				c,
			)
			return
		}
		common.ReqBadFailWithMessage(err.Error(), c)
		return

	}

	userName := c.MustGet(common.GIN_CTX_JWT_USER_NAME).(string)
	dbUser, err := models.GetUserByUserName(userName)
	if err != nil {
		sc.Logger.Error("通过token解析到的userName去数据库中找User失败",
			zap.Error(err),
		)
		common.ReqBadFailWithMessage(fmt.Sprintf("通过token解析到的userName去数据库中找User失败:%v", err.Error()), c)
		return
	}

	reqObj.UserID = dbUser.ID

	// 下面级联创建
	err = reqObj.UpdateOne()
	if err != nil {
		sc.Logger.Error("更新k8s-yaml任务数据库失败", zap.Any("k8s-yaml任务", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	common.OkWithMessage("创建成功", c)

}

func deleteK8sYamlTask(c *gin.Context) {
	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)
	// 校验一下 menu字段
	id := c.Param("id")
	sc.Logger.Info("删除k8s-yaml任务配置", zap.Any("id", id))

	// 先 去db中根据id找到这个对象
	intVar, _ := strconv.Atoi(id)
	dbObj, err := models.GetK8sYamlTaskById(intVar)
	if err != nil {
		sc.Logger.Error("根据id找k8s-yaml任务配置错误", zap.Any("k8s-yaml任务配置", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	err = dbObj.DeleteOne()
	if err != nil {
		sc.Logger.Error("根据id删除k8s-yaml任务配置错误", zap.Any("k8s-yaml任务配置", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}
	common.OkWithMessage("删除成功", c)
}

func getK8sYamlTaskList(c *gin.Context) {

	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)

	currentPage, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "100"))

	offset := 0
	limit := 0
	limit = pageSize
	if currentPage > 1 {
		offset = (currentPage - 1) * limit
	}
	// 这里只能新增 不能去掉，因为是 多种资源共用的query
	searchUserID := c.DefaultQuery("UserID", "")
	searchUserIDInt, _ := strconv.Atoi(searchUserID)
	searchName := c.DefaultQuery("name", "")

	// 	数据库中拿到所有的menu列表

	objs, err := models.GetK8sYamlTaskAll()
	if err != nil {
		sc.Logger.Error("去数据库中拿所有的k8s-yaml任务错误",
			zap.Error(err),
		)
		common.ReqBadFailWithMessage(fmt.Sprintf("去数据库中拿所有的k8s-yaml任务错误:%v", err.Error()), c)
		return
	}

	allIds := []int{}
	//// 遍历 role 准备menuIds 列表
	for _, obj := range objs {
		obj := obj

		// 在这里处理查询
		if searchName != "" && obj.Name != searchName {
			continue
		}

		// 在这里处理查询
		if searchUserID != "" && int(obj.UserID) != searchUserIDInt {
			continue
		}
		allIds = append(allIds, int(obj.ID))

		//role.MenuIds = menuIds
	}

	if len(allIds) == 0 {
		common.OkWithDetailed(allIds, "ok", c)
		return
	}
	objs, err = models.GetK8sYamlTaskByIdsWithLimitOffset(allIds, limit, offset)
	if err != nil {
		sc.Logger.Error("limit-offset去数据库中拿所有的任务错误",
			zap.Error(err),
		)
		common.ReqBadFailWithMessage(fmt.Sprintf("limit-offset去数据库中拿所有的脚本模板错误:%v", err.Error()), c)
		return
	}

	for _, obj := range objs {
		obj := obj

		obj.FillFrontAllData()

	}

	resp := &ResponseResourceCommon{
		//Total: models.GetK8sClusterCount(), 因为是带了查询条件的 所以 count 不能是all 而是 过滤后的total

		Total: len(allIds),
		Items: objs,
	}

	common.OkWithDetailed(resp, "ok", c)
}

func applyK8sYamlTaskOne(c *gin.Context) {
	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)
	id := c.Param("id")

	// 先 去db中根据id找到这个对象
	intVar, _ := strconv.Atoi(id)
	dbObj, err := models.GetK8sYamlTaskById(intVar)
	if err != nil {
		sc.Logger.Error("根据id找k8sYaml任务错误", zap.Any("k8sYaml任务", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	// 获取模板
	dbTemp, err := models.GetK8sYamlTemplateById(int(dbObj.TemplateId))
	if err != nil {
		sc.Logger.Error("根据id找k8syaml任务错误", zap.Any("k8sYaml任务", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	// 获取集群
	dbCluster, err := models.GetK8sClusterByName(dbObj.ClusterName)
	if err != nil {
		sc.Logger.Error("根据name找k8s集群错误", zap.Any("k8sYaml任务", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	// 从cache中通过 集群的id 拿到集群的clientSet
	kc := c.MustGet(common.GIN_CTX_K8s_CACHE).(*cache.K8sClusterCache)
	kSet := kc.GetClusterClientSetById(dbCluster.ID)
	if kSet == nil {
		msg := fmt.Sprintf("根据id找k8s集群kset缓存错误")
		sc.Logger.Error(msg, zap.Any("k8s集群配置", dbObj.ClusterName), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}
	dSet := kc.GetClusterDynamicClientById(dbCluster.ID)
	if kSet == nil {
		sc.Logger.Error("根据id找k8s集群dset缓存错误", zap.Any("k8s-yaml任务", dbObj.ClusterName), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	// 先拼接yaml-变量，拿到kset apply执行
	yamlString := dbTemp.Content
	for _, kv := range dbObj.Variables {
		kvs := strings.Split(kv, "=")
		if len(kvs) != 2 {
			continue
		}
		k := kvs[0]
		v := kvs[1]
		yamlString = strings.ReplaceAll(yamlString, k, v)
	}

	fmt.Println(yamlString)

	err = DynamicObjApply(kSet, dSet, sc.Logger, dbCluster.ActionTimeoutSeconds, []byte(yamlString))
	if err != nil {
		msg := fmt.Sprintf("DynamicObjApply创建或更新错误:%v", err.Error())
		dbObj.ApplyResult = err.Error()
		dbObj.UpdateOne()
		sc.Logger.Error(msg, zap.Any("k8s-yaml任务", dbObj.ClusterName), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	dbObj.Status = common.K8S_YAMLTASK_STATUS_APPLIED
	dbObj.ApplyResult = "成功"
	err = dbObj.UpdateOne()
	if err != nil {
		msg := fmt.Sprintf("更新任务状态错误:%v", err)
		sc.Logger.Error(msg, zap.Any("k8sYaml任务", id), zap.Error(err))
		common.FailWithMessage(msg, c)
		return
	}

	common.OkWithMessage("更新成功", c)
}

func DynamicObjApply(kClient kubernetes.Interface, dyClient dynamic.Interface, logger *zap.Logger, tw int, filebytes []byte) error {

	decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(filebytes), 4096)

	// 这里写for 的原因是 yaml 中 --- 分割多个 对象
	for {
		ext := runtime.RawExtension{}
		if err := decoder.Decode(&ext); err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
		// TODO: This needs to be able to handle object in other encodings and schemas.
		ext.Raw = bytes.TrimSpace(ext.Raw)
		if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) {
			continue
		}

		obj, gvk, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode(ext.Raw, nil, nil)
		if err != nil {
			return err
		}

		unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
		if err != nil {
			return err
		}

		unstructuredObj := &unstructured.Unstructured{Object: unstructuredMap}
		gr, err := restmapper.GetAPIGroupResources(kClient.Discovery())
		if err != nil {
			return err
		}

		mapper := restmapper.NewDiscoveryRESTMapper(gr)
		mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
		if err != nil {
			return err
		}
		var dri dynamic.ResourceInterface
		if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
			if unstructuredObj.GetNamespace() == "" {
				unstructuredObj.SetNamespace("default")
			}
			dri = dyClient.Resource(mapping.Resource).Namespace(unstructuredObj.GetNamespace())
		} else {
			dri = dyClient.Resource(mapping.Resource)
		}

		ctx1, canFun1 := common.GenTimeoutContext(tw)
		defer canFun1()
		// 先get 如果有就 update，如果没有就创建
		obj1, err := dri.Get(ctx1, unstructuredObj.GetName(), metav1.GetOptions{})
		if err != nil {
			if !errors.IsNotFound(err) {
				logger.Error("dyClient先获取对象失败",
					zap.Error(err),
					zap.Any("对象", unstructuredObj.GetName()),
				)
				continue

			}
			ctx2, canFun2 := common.GenTimeoutContext(tw)
			defer canFun2()
			obj1, err = dri.Create(ctx2, unstructuredObj, metav1.CreateOptions{})
			if err != nil {
				logger.Error("dyClient对象不存在创建失败",
					zap.Error(err),
					zap.Any("对象", unstructuredObj.GetName()),
				)
				continue
			}

			logger.Info("dyClient对象不存在创建成功",
				zap.Any("对象", obj1.GetName()),
				zap.Any("类型", gvk.String()),
			)

			continue
		}
		ctx3, canFun3 := common.GenTimeoutContext(tw)
		defer canFun3()
		obj1, err = dri.Update(ctx3, unstructuredObj, metav1.UpdateOptions{})
		if err != nil {
			logger.Error("dyClient对象存储更新失败",
				zap.Error(err),
				zap.Any("对象", obj1.GetName()),
			)
			continue
		}
		logger.Info("dyClient对象存储更新成功",
			zap.Error(err),
			zap.Any("对象", obj1.GetName()),
			zap.Any("类型", gvk.String()),
		)
	}
}
